// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/maicoin/order_v2/parser.h"

namespace coin2::exchange::maicoin::order_v2 {

bool MaicoinParser::IsMaicoinSpotErrMessage(const JsonValue& doc) {
  auto error_code = GetErrorCode(doc);
  auto error_msg = GetErrorMsg(doc);

  return !error_code || !error_msg || error_code.value() != 0 || !error_msg.value().empty();
}

int64_t MaicoinParser::GetProcOrderId(const JsonValue& data) {
  if (!data.HasMember("client_oid") || !data["client_oid"].IsString() ||
      data["client_oid"].IsNull()) {
    return 0;
  }

  return stoll(data["client_oid"].GetString());
}

int64_t MaicoinParser::GetWsProcOrderId(const JsonValue& data) {
  if (!data.HasMember("ci") || !data["ci"].IsString() || data["ci"].IsNull()) {
    return 0;
  }

  return stoll(data["ci"].GetString());
}

coin::proto::OrderSide MaicoinParser::GetOrderSide(const std::string& side) {
  if (side == "buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

coin::proto::OrderSide MaicoinParser::GetFillOrderSide(const std::string& side) {
  if (side == "bid") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "ask") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

coin::proto::OrderType MaicoinParser::GetOrderType(const std::string& type) {
  if (type == "limit" || type == "stop_limit" || type == "post_only" || type == "ioc_limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "market" || type == "stop_market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void MaicoinParser::SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if (status == "wait" || status == "convert" || status == "finalizing") {
    order->set_is_live(true);
  } else if (status == "cancel" || status == "failed") {
    order->set_is_live(false);
    order->set_cancel_confirmed(true);
  } else if (status == "done") {
    order->set_is_live(false);
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[SetOrderStatus] Unknown order status: " << status;
  }
}

void MaicoinParser::ParseAccountBalance() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(doc);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance MaicoinParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Maicoin");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeLower()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      *balance = std::move(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool MaicoinParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  [
    {
      "currency":"twd",
      "balance":"100000.0",
      "locked":"5566.0",
      "staked":"0.0",
      "type":"exchange",
      "fiat_currency":"twd",
      "fiat_balance":"100000.0"
    }
  ]
  */
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("balance") || !data.HasMember("locked")) {
      continue;
    }
    std::string currency(data["currency"].GetString());
    CurrencyBalance& currency_balance = (*raw_balance_map)[currency];
    currency_balance.set_currency_native(std::move(currency));
    auto avail = stod(data["balance"].GetString());
    auto hold = stod(data["locked"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_total(avail + hold);
    currency_balance.set_hold(hold);
  }

  return true;
}

void MaicoinParser::ParseOpenOrderInfo() { ParseOrderInfos(__func__); }

void MaicoinParser::ParseHistoryOrderInfo() { ParseOrderInfos(__func__); }

void MaicoinParser::ParseOrderInfos(std::string func_name) {
  /*
  [
    {
      "id":87,
      "client_oid":"4511dc1f-4b28-4adb-a384-109384d3bc6e",
      "side":"buy",
      "ord_type":"limit",
      "price":"21499.0",
      "stop_price":"21499.0",
      "avg_price":"21499.0",
      "state":"wait",
      "market":"ethtwd",
      "created_at":1521726960,
      "created_at_in_ms":1521726960123,
      "updated_at":1521726960,
      "updated_at_in_ms":1521726960123,
      "volume":"0.2658",
      "remaining_volume":"0.2658",
      "executed_volume":"0.0",
      "trades_count":0,
      "group_id":1
    }
  ]
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[" + func_name + "] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[" + func_name + "] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("market")) {
      continue;
    }
    std::string native_symbol = data["market"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void MaicoinParser::ParseOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Maicoin);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v2");
  order->set_exchange_order_id(data["id"].GetString());
  order->set_symbol(data["market"].GetString());
  order->set_price(stod(data["price"].GetString()));
  order->set_qty(std::fabs(stod(data["volume"].GetString())));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data["side"].GetString()));
  order->set_order_type(GetOrderType(data["ord_type"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data["state"].GetString());
}

void MaicoinParser::ParseWsOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Maicoin);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v2");
  order->set_exchange_order_id(data["i"].GetString());
  order->set_symbol(data["M"].GetString());
  order->set_price(stod(data["p"].GetString()));
  order->set_qty(std::fabs(stod(data["v"].GetString())));
  order->set_proc_order_id(GetWsProcOrderId(data));
  order->set_side(GetFillOrderSide(data["sd"].GetString()));
  order->set_order_type(GetOrderType(data["ot"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data["S"].GetString());
}

void MaicoinParser::ParseFillOrderInfo() {
  /*
  [
    {
      "id":68444,
      "price":"21499.0",
      "volume":"0.2658",
      "funds":"5714.4",
      "market":"ethtwd",
      "market_name":"ETH/TWD",
      "created_at":1521726960,
      "created_at_in_ms":1521726960357,
      "side":"bid",
      "fee":"string",
      "fee_currency":"string",
      "order_id":87,
      "info":{
        "maker":"ask",
        "ask":{
          "fee":"1.0",
          "fee_currency":"twd",
          "order_id":1
        }
      }
    }
  ]
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseFillOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseFillInfo(fill, data);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void MaicoinParser::ParseFillInfo(ProductFillElement* fill, const JsonValue& data) {
  fill->set_symbol(data["market"].GetString());
  fill->set_exchange_order_id(data["order_id"].GetString());
  fill->set_price(stod(data["price"].GetString()));
  fill->set_qty(std::fabs(stod(data["volume"].GetString())));
  fill->set_fill_id(data["id"].GetString());
  fill->set_side(GetFillOrderSide(data["side"].GetString()));
  if (data.HasMember("fee") && !data["fee"].IsNull() && data["fee"].IsString() &&
      data.HasMember("fee_currency") && !data["fee_currency"].IsNull() &&
      data["fee_currency"].IsString()) {
    fill->set_fee(stod(data["fee"].GetString()));
    fill->set_fee_currency(data["fee_currency"].GetString());
  }
  fill->set_fill_type(
      data.HasMember("info")
          ? (data["info"].HasMember("maker") ? coin::proto::FillType::MAKER_FILL_TYPE
                                             : coin::proto::FillType::TAKER_FILL_TYPE)
          : coin::proto::FillType::UNKNOWN_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void MaicoinParser::ParseWsFillInfo(ProductFillElement* fill, const JsonValue& data) {
  fill->set_symbol(data["M"].GetString());
  fill->set_exchange_order_id(data["oi"].GetString());
  fill->set_price(stod(data["p"].GetString()));
  fill->set_qty(std::fabs(stod(data["v"].GetString())));
  fill->set_fill_id(data["i"].GetString());
  fill->set_side(GetFillOrderSide(data["sd"].GetString()));
  if (data.HasMember("f") && !data["f"].IsNull() && data["f"].IsString() && data.HasMember("fc") &&
      !data["fc"].IsNull() && data["fc"].IsString()) {
    fill->set_fee(stod(data["f"].GetString()));
    fill->set_fee_currency(data["fc"].GetString());
  }
  fill->set_fill_type(
      data["m"].GetBool() ? coin::proto::FillType::MAKER_FILL_TYPE
                          : coin::proto::FillType::TAKER_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void MaicoinParser::ParseSubmitResponse() {
  /*
  {
    "id":87,
    "client_oid":"4511dc1f-4b28-4adb-a384-109384d3bc6e",
    "side":"buy",
    "ord_type":"limit",
    "price":"21499.0",
    "stop_price":"21499.0",
    "avg_price":"21499.0",
    "state":"wait",
    "market":"ethtwd",
    "created_at":1521726960,
    "created_at_in_ms":1521726960123,
    "updated_at":1521726960,
    "updated_at_in_ms":1521726960123,
    "volume":"0.2658",
    "remaining_volume":"0.2658",
    "executed_volume":"0.0",
    "trades_count":0,
    "group_id":1
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto exchange_error_msg = GetErrorMsg(json);

  if (exchange_error_code && exchange_error_msg) {
    auto error_code = GetOrderErrorCode(exchange_error_code.value());
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(std::to_string(exchange_error_code.value()));
    resp->set_exchange_error_msg(exchange_error_msg.value());
  }

  if (resp->error_code() != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("id") || !json["id"].IsString()) {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  resp->set_exchange_order_id(json["id"].GetString());
  resp->set_success(true);
}

void MaicoinParser::ParseCancelResponse() {
  /*
  {
    "id":87,
    "client_oid":"4511dc1f-4b28-4adb-a384-109384d3bc6e",
    "side":"buy",
    "ord_type":"limit",
    "price":"21499.0",
    "stop_price":"21499.0",
    "avg_price":"21499.0",
    "state":"wait",
    "market":"ethtwd",
    "created_at":1521726960,
    "created_at_in_ms":1521726960123,
    "updated_at":1521726960,
    "updated_at_in_ms":1521726960123,
    "volume":"0.2658",
    "remaining_volume":"0.2658",
    "executed_volume":"0.0",
    "trades_count":0,
    "group_id":1
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto exchange_error_msg = GetErrorMsg(json);

  if (exchange_error_code && exchange_error_msg) {
    auto error_code = GetOrderErrorCode(exchange_error_code.value());
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(std::to_string(exchange_error_code.value()));
    resp->set_exchange_error_msg(exchange_error_msg.value());
  }

  if (resp->error_code() != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("id") || !json.HasMember("state")) {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  resp->set_exchange_order_id(json["id"].GetString());
  resp->set_success(true);

  if (std::string(json["state"].GetString()) == "cancel") {
    resp->set_cancel_confirmed(true);
  }
}

int32_t MaicoinParser::ParseErrorMessage(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto http_code = http_context->res.result_int();
  if (http_code == 401) {
    LOG(ERROR) << "[ParseErrorMessage] Unauthorized-Authentication failed\n" << msg_->DebugString();
  } else if (http_code == 403) {
    LOG(ERROR) << "[ParseErrorMessage] Access denied\n" << msg_->DebugString();
  } else if (http_code == 404) {
    LOG(ERROR) << "[ParseErrorMessage] Request path not found\n" << msg_->DebugString();
  } else if (http_code == 429) {
    // auto enable_time = 5 * 1'000'000'000LL + GetCurrentTimestamp();
    // mb_->PublishEvent("maicoin/enable_time", reinterpret_cast<void*>(enable_time));
    LOG(ERROR) << "[ParseErrorMessage] Rate Limit Exceeded\n" << msg_->DebugString();
  } else if (http_code == 500) {
    LOG(WARNING) << "[ParseErrorMessage] Maicoin service problem\n" << msg_->DebugString();
  }

  return http_code;
}

void MaicoinParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  {
    "c":"user",
    "e":"order_snapshot",
    "o":[
      {
        "i":87,
        "sd":"bid",
        "ot":"limit",
        "p":"21499.0",
        "sp":"21499.0",
        "ap":"21499.0",
        "v":"0.2658",
        "rv":"0.0",
        "ev":"0.2658",
        "S":"done",
        "M":"ethtwd",
        "tc":1,
        "T":1659419048000,
        "TU":1659419048406,
        "gi":123,
        "ci":"client-oid-1"
      }
    ],
    "T":1521726960357
  }
  */
  if (!document.HasMember("o") || !document["o"].IsArray()) {
    LOG(ERROR) << "[ParseWsOrderInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();

  bool product_order_bundle_found = false;
  for (const auto& data : document["o"].GetArray()) {
    auto order = product_order_bundle->add_each_order();

    ParseWsOrderInfo(order, data);

    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void MaicoinParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  {
    "c":"user",
    "e":"trade_snapshot",
    "t":[
      {
        "i":68444,
        "M":"ethtwd",
        "sd":"bid",
        "p":"21499.0",
        "v":"0.2658",
        "f":"3.2",
        "fc":"twd",
        "T":1659216053748,
        "TU":1659216054046,
        "m":true,
        "oi":3253823664
      }
    ],
    "T":1659412100259
  }
  */
  if (!document.HasMember("t") || !document["t"].IsArray()) {
    LOG(ERROR) << "[ParseWsTradeInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();

  bool product_fill_bundle_found = false;
  for (const auto& data : document["t"].GetArray()) {
    if (!data.HasMember("i")) {
      continue;
    }

    auto fill = product_fill_bundle->add_each_fill();

    ParseWsFillInfo(fill, data);

    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void MaicoinParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  {
    "c":"user",
    "e":"account_update",
    "B":[
      {
        "cu":"btc",
        "av":"123.4",
        "l":"0.5",
        "stk":null,
        "TU":1659390246343
      }
    ],
    "T":1659412100181
  }
  */
  if (!is_account_balance_initialized_) {
    return;
  }

  if (!document.HasMember("B") || !document["B"].IsArray()) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Maicoin");
  acc_balance->set_market_type("Spot");

  bool account_balance_found = false;
  for (const auto& data : document["B"].GetArray()) {
    if (!data.HasMember("cu") || !data.HasMember("av") || !data.HasMember("l")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["cu"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["cu"].GetString());
    auto* balance = acc_balance->add_each_balance();
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    auto avail = stod(data["av"].GetString());
    auto hold = stod(data["l"].GetString());
    balance->set_available(avail);
    balance->set_total(avail + hold);
    balance->set_hold(hold);
    account_balance_found = true;
  }

  if (account_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void MaicoinParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);

  if (!document.HasMember("e") || !document["e"].IsString()) {
    LOG(ERROR) << "[ParseWsMessage] Invalid data format\n" << JsonToString(document);
    return;
  }

  std::string event(document["e"].GetString());
  if (event == "order_snapshot" || event == "order_update") {
    ParseWsOrderInfoList(document);
  } else if (event == "trade_snapshot" || event == "trade_update") {
    ParseWsTradeInfoList(document);
  } else if (event == "account_snapshot" || event == "account_update") {
    ParseWsBalanceInfoList(document);
  }
}

auto MaicoinParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  ParseErrorMessage(msg.http_context);

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseAccountBalance();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseHistoryOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseFillOrderInfo();
      break;
    default: {
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::maicoin::order_v2
